/* this file is part of the LME project
 * for more informations visit : http://code.google.com/p/lme
 * Copyright (2010) : Nicolas Brunie 
 */

#include <GL/glew.h>
#include <stdexcept>
#include <string>
#include <map>
#include <vector>
#include <iostream>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>

#ifndef __LME_SHADER_HPP__
#define __LME_SHADER_HPP__

using namespace std;

class ShaderUniformAttribute {
	protected:
		GLuint id;
		string attributeName;
	public:
		ShaderUniformAttribute(string aname_) : id(-1), attributeName(aname_) {};
		void getLocation(GLuint handle_) {
			const char* an = attributeName.c_str();
			id = glGetUniformLocation(handle_, an);
			//std::cout << "DEBUG : Uniform Attribute " << an << " has been bind to id " << id << " for program handle " << handle_ << endl;
		};

		/** this function will be called each time the shaders with this attribute is used */
		virtual void bind() {};
};

class IntegerUniform : public ShaderUniformAttribute {
	protected:
		int value;
	public:
		IntegerUniform(string name_) : ShaderUniformAttribute(name_) {};
		void setValue(int value_) { value = value_;};

		virtual void bind() {
			//cout << "DEBUG : setting uniform " << id << " to value " << value << endl;
			glUniform1i(id, value);
		};
};

class Vec4Uniform : public ShaderUniformAttribute {
	protected:
		glm::vec4 value;
	public:
		Vec4Uniform(string name_) : ShaderUniformAttribute(name_) {};
		void setValue(glm::vec4 value_) { value = value_;};

		virtual void bind() {
			//cout << "DEBUG : setting uniform " << id << " to value " << value.x << "," << value.y << "," << value.z << "," << value.w << endl;
			glUniform4f(id, value.x, value.y, value.z, value.w);
		};
};

		



class LME_Shader {
	protected:
		map<string, int> m_uniformIdBind;
		string _name;
		string _code;
		GLuint _handle;
		GLint _compiled;
	
		LME_Shader (const string &filename);
		void compile () 
			throw (std::runtime_error);
		void loadShaderFile (const string &filename) 
		 throw (std::runtime_error);

	public:
		GLuint bindUniform(string uniform_);
		virtual ~LME_Shader ();
		const string &name () const { return _name; }
		const string &code () const { return _code; }
		GLuint handle () const { return _handle; }
		bool fail () const { return (_compiled == GL_FALSE); }

		virtual GLenum shaderType () const = 0;

		void printInfoLog () const;
};


class LME_VertexShader : public LME_Shader {
	public:
		LME_VertexShader (const string &filename);

		virtual GLenum shaderType () const {
			if (GLEW_VERSION_2_0) return GL_VERTEX_SHADER;
			else return GL_VERTEX_SHADER_ARB;
		}
};

class LME_FragmentShader : public LME_Shader {
	public:
		// Constructor
		LME_FragmentShader(const string& filename_);

		virtual GLenum shaderType() const {
			if(GLEW_VERSION_2_0) return GL_FRAGMENT_SHADER;
			else return GL_FRAGMENT_SHADER_ARB;
		};
};


class LME_ShaderProgram {
	private:
		string _name;
		GLuint _handle;
		GLint _linked;

		map<string, GLuint> m_uniformBinding;

		vector<ShaderUniformAttribute*> m_uniformAttributes;

		/* use custom "use" (binding argument)*/
		bool m_custom;
	public:
		// Constructor/destructor
		LME_ShaderProgram (const string &name,
		  	 const LME_VertexShader &vertexShader,
		  	 const LME_FragmentShader &fragmentShader);
		~LME_ShaderProgram ();

		// Public interface
		void use ();
		void unuse () const;
		void printInfoLog () const;

		void bindAttribute(ShaderUniformAttribute* attribute_) {
			m_uniformAttributes.push_back(attribute_);
			attribute_->getLocation(_handle);
			m_custom = true;
		};

		// Accessors
		const string &name () const { return _name; }
		GLuint handle () const { return _handle; }
		bool fail () const { return (_linked == GL_FALSE); }
};

#endif
